package org.budo.support.zxing.util;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

import javax.imageio.ImageIO;

import org.budo.support.javax.imageio.util.ImageIoUtil;
import org.budo.support.zxing.BufferedImageLuminanceSource;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
 * @author lmw
 */
public class QRCodeUtil {
    private static final String CHARSET = "utf-8";

    private static final String FORMAT_NAME = "JPG";

    // 二维码尺寸
    private static final int QRCODE_SIZE = 1000;

    public static BufferedImage createImage(String content, String imgPath, boolean needCompress) {
        Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);

        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        BitMatrix bitMatrix = encode(multiFormatWriter, content, hints);

        BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
        return image;
    }

    private static BitMatrix encode(MultiFormatWriter multiFormatWriter, String content, Map<EncodeHintType, Object> hints) {
        try {
            return multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
        } catch (WriterException e) {
            throw new RuntimeException(e);
        }
    }

    public static void encode(String content, String imgPath, String destPath, boolean needCompress) throws Exception {
        BufferedImage image = createImage(content, imgPath, needCompress);
        mkdirs(destPath);
        Random random = ThreadLocalRandom.current();
        String file = random.nextInt(99999999) + ".jpg";
        ImageIO.write(image, FORMAT_NAME, new File(destPath + "/" + file));
    }

    public static void encodeWithName(String content, String destPath, String filename) throws Exception {
        BufferedImage image = createImage(content, destPath, false);
        ImageIO.write(image, FORMAT_NAME, new File(destPath + "/" + filename));
    }

    public static void mkdirs(String destPath) {
        File file = new File(destPath); // 当文件夹不存在时，mkdirs会自动创建多层目录，区别于mkdir．(mkdir如果父目录不存在则会抛出异常)
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }

    public static void encode(String content, String imgPath, String destPath) throws Exception {
        encode(content, imgPath, destPath, false);
    }

    public static void encode(String content, String destPath, boolean needCompress) throws Exception {
        encode(content, null, destPath, needCompress);
    }

    public static void encode(String content, String destPath) throws Exception {
        encode(content, null, destPath, false);
    }

    public static void encode(String content, String imgPath, OutputStream output, boolean needCompress) {
        BufferedImage image = createImage(content, imgPath, needCompress);
        ImageIoUtil.write(image, FORMAT_NAME, output);
    }

    public static BufferedImage encode(String content) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        encode(content, byteArrayOutputStream);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        return ImageIoUtil.read(byteArrayInputStream);
    }

    public static void encode(String content, OutputStream output) {
        encode(content, null, output, false);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static String decode(File file) throws Exception {
        BufferedImage image;
        image = ImageIO.read(file);
        if (image == null) {
            return null;
        }

        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        Result result;
        Hashtable hints = new Hashtable();
        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
        result = new MultiFormatReader().decode(bitmap, hints);
        String resultStr = result.getText();
        return resultStr;
    }

    public static String decode(String path) throws Exception {
        return decode(new File(path));
    }
}
